/*
 * File     : NativeUnsafeAccess.java
 * Created  : 15 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal;

import java.nio.Buffer;
import java.nio.ByteBuffer;

import com.googlecode.dni.Allocator;
import com.googlecode.dni.internal.MemoryAccess.MemoryAccessor;
import com.googlecode.dni.type.Pointer;


/**
 * Equivalent to {@link SunMiscUnsafeAccess}, but where
 * <code>sun.misc.Unsafe</code> is unavailable.  Also provides some other
 * functions that are generally useful.
 *
 * @author Matthew Wilson
 */
// TODO v1.1 probably merge this with MemoryAccess to reduce the number of types
public final class NativeUnsafeAccess
{

    /** Prevents instantiation. */
    private NativeUnsafeAccess()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * @return an allocator instance
     */
    public static Allocator getAllocator()
    {
        return new AllocatorImpl();
    }

    /**
     * @return an accessor impl
     */
    public static MemoryAccessor getMemoryAccessor()
    {
        return new MemoryAccessorImpl();
    }

    /**
     * Allocates memory.
     *
     * @param size
     *            the size in bytes
     * @return pointer to the memory
     */
    public static native long allocateMemory( long size );

    /**
     * Copies memory.
     *
     * @param from
     *            pointer to the source
     * @param to
     *            pointer to the destination
     * @param size
     *            the size in bytes
     */
    public static native void copyMemory( long from, long to, long size );

    /**
     * Frees memory.
     *
     * @param pointer
     *            pointer to the memory
     */
    public static native void freeMemory( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the boolean value
     */
    public static native boolean getBoolean( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the byte value
     */
    public static native byte getByte( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the char value
     */
    public static native char getChar( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the double value
     */
    public static native double getDouble( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the float value
     */
    public static native float getFloat( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the int value
     */
    public static native int getInt( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the long value
     */
    public static native long getLong( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the short value
     */
    public static native short getShort( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @return the pointer value
     */
    public static native long getPointer( long pointer );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the boolean value
     */
    public static native void putBoolean( long pointer, boolean value );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the byte value
     */
    public static native void putByte( long pointer, byte value );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the char value
     */
    public static native void putChar( long pointer, char value );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the double value
     */
    public static native void putDouble( long pointer, double value );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the boolean value
     */
    public static native void putFloat( long pointer, float value );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the int value
     */
    public static native void putInt( long pointer, int value );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the long value
     */
    public static native void putLong( long pointer, long value );

    /**
     * @param pointer
     *            the pointer
     * @param value
     *            the short value
     */
    public static native void putShort( long pointer, short value );

    /**
     * Sets memory to the given value.
     *
     * @param pointer
     *            the pointer
     * @param length
     *            the number of bytes
     * @param value
     *            the value to set
     */
    public static native void setMemory( long pointer, long length, byte value );

    /**
     * Copies bytes from the array to memory.
     *
     * @param pointer
     *            the destination memory
     * @param array
     *            the array
     * @param size
     *            the number of bytes to copy
     */
    public static native void copyFromArray( long pointer, byte[] array, int size );

    /**
     * Defines a class.
     *
     * @param name
     *            the class name
     * @param classLoader
     *            the class loader
     * @param buffer
     *            the array containing a class file
     * @param bufferLength
     *            the size of the array
     * @return a class
     */
    public static native Class< ? > defineClass( String name, ClassLoader classLoader, byte[] buffer, int bufferLength );

    /**
     * Obtains the pointer to the base of the given buffer's memory.
     *
     * @param buffer
     *            the buffer
     * @return the pointer
     */
    public static long getBufferPointer( final Buffer buffer )
    {
        if ( !buffer.isDirect() )
        {
            throw new IllegalArgumentException();
        }

        return NativeUnsafeAccess.getBufferPointer0( buffer );
    }

    /**
     * Creates a buffer (native endian).
     *
     * @param pointer
     *            the pointer
     * @param size
     *            the size in bytes
     * @return a buffer
     */
    public static ByteBuffer getBuffer( final long pointer, final int size )
    {
        return getBuffer0( pointer, size ).order( DniInternal.NATIVE_ENDIAN );
    }

    /**
     * Creates a {@link ByteBuffer} (undefined endian) for the given region of
     * memory.
     *
     * @param pointer
     *            the pointer
     * @param size
     *            the size in bytes
     * @return a buffer
     */
    private static native ByteBuffer getBuffer0( long pointer, int size );

    /**
     * Obtains the pointer to the base of the given buffer's memory.
     *
     * @param buffer
     *            the buffer
     * @return the pointer
     */
    private static native long getBufferPointer0( Buffer buffer );


    //-- String Functions ------------------------------------------------------

    /**
     * Writes out a UTF-8 string.  Use this when the string is "long".
     *
     * @param string
     *            the string
     * @param pointer
     *            the buffer for writing
     *
     * @return number of bytes written, including terminator
     */
    public static native int writeUtf8String( String string, long pointer );

    /**
     * Writes out an ISO-8859-1 string.  Use this when the string is "long".
     *
     * @param string
     *            the string
     * @param pointer
     *            the buffer for writing
     *
     * @return number of bytes written, including terminator
     */
    public static native int writeIso88591String( String string, long pointer );

    /**
     * Writes out an ASCII string.  Use this when the string is "long".
     *
     * @param string
     *            the string
     * @param pointer
     *            the buffer for writing
     *
     * @return number of bytes written, including terminator
     */
    public static native int writeAsciiString( String string, long pointer );

    /**
     * Writes out a UTF-16 string.  Use this when the string is "long".
     *
     * @param string
     *            the string
     * @param pointer
     *            the buffer for writing
     *
     * @return number of bytes written, including terminator
     */
    public static native int writeUtf16String( String string, long pointer );

    /**
     * Writes out a string using a lookup table.  Use this when the string is
     * "long".
     *
     * @param string
     *            the string
     * @param pointer
     *            the buffer for writing
     * @param lookupPointer
     *            pointer to a lookup table
     * @param lookupSize
     *            the number of entries in the lookup table
     *
     * @return number of bytes written, including terminator
     */
    public static native int writeLookupString( String string,
                                                long pointer,
                                                long lookupPointer,
                                                int lookupSize );

    /**
     * Reads a UTF-8 string.
     *
     * @param pointer
     *            the pointer
     *
     * @return the string
     */
    public static native String readUtf8String( long pointer );

    /**
     * Reads an ISO-8859-1 string.  Also works for ASCII.
     *
     * @param pointer
     *            the pointer
     *
     * @return the string
     */
    public static native String readIso88591String( long pointer );

    /**
     * Reads a UTF-16 string.
     *
     * @param pointer
     *            the pointer
     *
     * @return the string
     */
    public static native String readUtf16String( long pointer );

    /**
     * Reads a string with a lookup table.
     *
     * @param pointer
     *            the pointer
     * @param lookupPointer
     *            pointer to a (reverse) lookup table
     *
     * @return the string
     */
    public static native String readLookupString( long pointer, long lookupPointer );


    //--------------------------------------------------------------------------


    private static final class AllocatorImpl implements Allocator
    {

        /**
         * {@inheritDoc}
         */
        @Override
        public Pointer allocate( final long size )
        {
            long pointer = allocateMemory( size );
            if ( pointer == 0 )
            {
                throw new OutOfMemoryError();
            }

            setMemory( pointer, size, (byte) 0 );

            return Pointer.fromAddress( pointer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Pointer copy( final Pointer sourcePointer, final long size )
        {
            long pointer = allocateMemory( size );
            if ( pointer == 0 )
            {
                throw new OutOfMemoryError();
            }

            copyMemory( sourcePointer.address(), pointer, size );

            return Pointer.fromAddress( pointer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void free( final Pointer pointer )
        {
            if ( Pointer.isNull( pointer ) )
            {
                return;
            }
            freeMemory( pointer.address() );
        }
    }

    private static final class MemoryAccessorImpl implements MemoryAccessor
    {
        /** {@inheritDoc} */
        @Override
        public byte getByte( final long pointer )
        {
            return NativeUnsafeAccess.getByte( pointer );
        }

        /** {@inheritDoc} */
        @Override
        public void putByte( final long pointer, final byte value )
        {
            NativeUnsafeAccess.putByte( pointer, value );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public char getChar( final long pointer )
        {
            return NativeUnsafeAccess.getChar( pointer );
        }

        /** {@inheritDoc} */
        @Override
        public void putChar( final long pointer, final char value )
        {
            NativeUnsafeAccess.putChar( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public int getInt( final long pointer )
        {
            return NativeUnsafeAccess.getInt( pointer );
        }

        /** {@inheritDoc} */
        @Override
        public void putInt( final long pointer, final int value )
        {
            NativeUnsafeAccess.putInt( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public long getLong( final long pointer )
        {
            return NativeUnsafeAccess.getLong( pointer );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void putLong( final long pointer, final long value )
        {
            NativeUnsafeAccess.putLong( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public void putDouble( final long pointer, final double value )
        {
            NativeUnsafeAccess.putDouble( pointer, value );
        }

        /** {@inheritDoc} */
        @Override
        public long getBufferPointer( final Buffer buffer )
        {
            return NativeUnsafeAccess.getBufferPointer( buffer );
        }

        /** {@inheritDoc} */
        @Override
        public ByteBuffer getBuffer( final long pointer, final int size )
        {
            return NativeUnsafeAccess.getBuffer( pointer, size );
        }

        /** {@inheritDoc} */
        @Override
        public void copyMemory( final long from, final long to, final long size )
        {
            NativeUnsafeAccess.copyMemory( from, to, size );
        }

        /** {@inheritDoc} */
        @Override
        public void zeroMemory( final long pointer, final long size )
        {
            NativeUnsafeAccess.setMemory( pointer, size, (byte) 0 );
        }
    }

}

